#include <iostream>
#include <cstdlib>
#include <cstring>
#include <time.h>
#include <fstream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <vector>
#include <queue>
#include "Blowfish.h"
#include "Packet.h"
#include "PracticalSocket.h"

using namespace std;

// Why you no use class/structs?

/*
 * CompareTimeout is a class which acts as a comparator for packets in the window vector
 * which are pointed to by timeoutQueue.  Highest priority is the packet with the lowest timeout timestamp.
 */
// @TODO: Make sure the correct thread is on top.
class CompareTimeout {
	public:
	bool operator()(const Packet* p1, const Packet* p2) // Returns true if p1 is smaller than p2
	{
		if (p1->timeout.tv_sec < p2->timeout.tv_sec) return true;
		if (p1->timeout.tv_sec == p2->timeout.tv_sec && p1->timeout.tv_nsec < p2->timeout.tv_nsec) return true;
		return false;
	}
};

// Will be replaced by calls to client->get___(); of some sort client->getServer(___)->getwindowSize()
// Unless returned value is 0, in which case use a default.
int senderWinSize = 4; // 1 = sw
int recvWinSize = 1; // 1 = gbn or sw, >1 sr. Always < senderWinSize
int sequenceNumberRange = 2 * senderWinSize;
int timeoutInterval = 0; // nanoseconds
const int packetSize = 1024 + PADDING_SIZE; // Allow extra padding for encryption

// These variables control the window and numbers
int seqNum = 0;
int packNum = 0;
int expectedSeqNum = 0;

// Vector representing the current window.
vector<Packet*> window(senderWinSize, NULL);
// PriorityQueue of pointers to the same locations as above.
// Used to determine timeouts
priority_queue<Packet*, vector<Packet*>, CompareTimeout> timeoutQueue;

// Buffer for reading in from file
char buffer[packetSize - HEADER_SIZE];
// File for reading in what is to be parsed
// @TODO ADD file name to send Start.txt, 1 for each connection, or lazy way is to make all transfer same file?
// We could leave as command line but I dun know about that
ifstream myFile;
// encryption object
Blowfish bf;

// socket to send/recv on
UDPSocket sock;
// servAddress and port will be known from other client
string servAddress;
unsigned short servPort;

// Mutex for controling access to window Objects
pthread_mutex_t windowMutex = PTHREAD_MUTEX_INITIALIZER; 
// mutex for controlling when to move the window and add new packets
pthread_mutex_t senderMutex = PTHREAD_MUTEX_INITIALIZER;

// Methods
void* threadRecv(void*);
void* threadTimer(void*);
timespec diff(timespec, timespec);

int main(int argc, char *argv[]) {
	if ((argc < 4) || (argc > 5)) {   // Test for correct number of arguments
		cerr << "Usage: " << argv[0] << " <Server> <File> <Server Port>\n";
		exit(1);
	}
	
	// Get server address and file name to send
	servAddress = argv[1];
	char* file = argv[2];

	// Seems odd to have the this here, particularly when it is no where else in the code
	// I would make an iterator when/if it was necessary.
	vector<Packet*>::iterator windowIt;

	// @TODO: Be sure to print out everything Tan asks for int the writeup.
	cout << "Configuring timeout" << endl;
	// Get the average timeout.
	// This is cool, I didn't know this was do-able.
	FILE *p = popen("ping -c 3 ec2-50-16-179-130.compute-1.amazonaws.com", "r"); // @TODO Change
	if (!p)	{
		perror("popen failed");
		return -1;
	}
	char pbuffer[256] = { 0 };
	while (fgets(pbuffer, sizeof(pbuffer), p)) {
		string out(pbuffer, 3);
		if (out.compare("rtt") == 0) {
			char temp[5];
			memcpy(temp, pbuffer + 29, 5);
			timeoutInterval = (int) (atof(temp) * 400 * 1000000); // ns
cout << timeoutInterval << endl;
		}
	}
	if (p) pclose(p);

	// Get port and resolve socket, argv3 is port from other client.
	servPort = Socket::resolveService(argv[3]);

	// Set encryption key
	// client->getKeyS()
	bf.Set_Passwd("123456789pass");
	
	// Begin ftp activities.
	try {
		// open file for sending.
		myFile.open(file, ios::in | ios::binary);
		
		// Split off reciever thread
		// Handles ack recv, setting ack received.
		pthread_t threadID; // Thread ID from pthread_create().
		if (pthread_create(&threadID, NULL, threadRecv, NULL) != 0) {
			cerr << "Unable to create thread" << endl;
			exit(1);
		}

		// Split off timer thread
		pthread_t timerThreadID; // Thread ID from pthread_create().
		if (pthread_create(&timerThreadID, NULL, threadTimer, NULL) != 0) {
			cerr << "Unable to create thread" << endl;
			exit(1);
		}

		bool done = false;

		// Fill window and send files
		// Loop thru window and send any that have not recieved acks or have not been sent
		while (!done) {
			if (myFile.is_open() && !myFile.eof()) {
				// block until ack received for first window frame
				pthread_mutex_lock(&senderMutex);
				// Fill window with packets and send them 
				pthread_mutex_lock(&windowMutex);

				// Global timeout for GBN
				timespec gbnTimeout;
				clock_gettime(CLOCK_REALTIME, &gbnTimeout);
				for (int index = 0; index < senderWinSize; index++) {
					if ((window.at(index) == NULL || !(*window.at(index)).sent) && !myFile.eof()) {
						// @TODO: Make this work with the reset method you gots
						// @TODO: Problem, on reset/new, the old pointer thing must be removed from the timeout que
						// @TODO: Therefore, in the timeout, we will need to do a check for the reset or null pointer
						// in that way we will be able to add it to the queue normally below, and just toss aside any bad packet pointers/any packet that has been reset since being added to the queue in the timeout thread
						memset(buffer, 0, packetSize - HEADER_SIZE);
						myFile.read(buffer, packetSize - HEADER_SIZE - PADDING_SIZE);
						short sizeRead = myFile.gcount();

						Packet* pb;
						if (window.at(index) == NULL) {
							pb = new Packet(packetSize, &bf);
						} else {
							pb = window.at(index);
						}

						pb->packetNum = packNum;
						pb->seqNum = seqNum;
						pb->setPayload(buffer, sizeRead, true);
						pb->setEof(myFile.eof());
						pb->sent = true;
						if (senderWinSize > 1 && recvWinSize == 1) { // GBN
							pb->timeout = gbnTimeout;
						} else {
							clock_gettime(CLOCK_REALTIME, &pb->timeout);
						}
						// @TODO Add errors
						// Errors will be available as a global map
						// use char* errorType = getErrorType(int sequenceNumber);
						if (pb->errorType != LOST_PACK) {
							sock.sendTo(pb->buildPacket(), packetSize, servAddress, servPort);
						}

						if (window.at(index) == NULL) {
							window.at(index) = pb;
                                                }
						// @TODO: add packet pointer to timeout queue
						timeoutQueue.push(pb);

						cout << "!!!!!!!!! SEND !!!!!!!!!!" << endl;
						cout << "Start: " << expectedSeqNum << " End: " << (expectedSeqNum + senderWinSize - 1) % sequenceNumberRange << endl;
						cout << "Packet: " << packNum << " Seq: " << seqNum << " sent" << endl;
						cout << endl;

						seqNum = (seqNum + 1) % sequenceNumberRange;
						packNum++;

						if (pb->isEof()) {
							done = true;
						}
/*
						// EOF
						if (myFile.eof()) {
							done = true;
							for (unsigned int i = 0; i < window.size(); i++) {
								if ((*window.at(i)).hasReset && !(*window.at(i)).sent) {
									done = false;
									break;
								}
							}
						}
*/
					}
				}
				// unlock modification mutex, but leave sending mutex locked
				pthread_mutex_unlock(&windowMutex);
			}
			// @TODO End Check
		}

		myFile.close();
	} catch (SocketException &e) {
		cerr << e.what() << endl;
		exit(1);
	}
	return 0;
}

// Thread method for recving acks
void* threadRecv (void* ti) {
	// allows thread resources to be reclaimed on termination
	pthread_detach(pthread_self());
	char* ackBuff = new char[packetSize - HEADER_SIZE];
	// Forever! wait for recv.
	for (;;) {
		// Clear ackbuff
		memset(ackBuff, 0, packetSize - HEADER_SIZE);
		// Wait for ack.
		sock.recv(ackBuff, packetSize);
		// build ack packet.
		Packet ack(ackBuff, packetSize, &bf);
		// Wait for windowMutex for threadsafety
		pthread_mutex_lock(&windowMutex);
		// Get index in window.
		int index = ack.seqNum % senderWinSize;
		if (window.at(index) != NULL) {
			// Check for lost ack error
			if ((*window.at(index)).errorType != LOST_ACK) {
				// Set ack in packet
				(*window.at(index)).ackRecieved = true;
				// Print ack
				cout << endl << endl << "Ack " << ack.seqNum << " Recieved" << endl;
				// If the ack is for the first slot in window
				cout << "curr: " << ack.seqNum % senderWinSize << " WindowStart: " << expectedSeqNum << endl;
				if (index == expectedSeqNum % senderWinSize) {
					// Slide the window
					index = expectedSeqNum % senderWinSize;
					while (window.at(index) != NULL && (*window.at(index)).ackRecieved) {
						cout << "Next window: " << (expectedSeqNum + 1) % sequenceNumberRange << " - " << (((expectedSeqNum + 1) % sequenceNumberRange) + senderWinSize - 1) % sequenceNumberRange << endl;
						expectedSeqNum = (expectedSeqNum + 1) % sequenceNumberRange;
						// Reset packet
						(*window.at(index)).reset();
						(*window.at(index)).hasReset = true;

						index = expectedSeqNum % senderWinSize;
					}

					// Unlock thread mutex so that the sender can update the fill the packet and send it.
					pthread_mutex_unlock(&senderMutex);
				} else if (senderWinSize > 1 && recvWinSize == 1) { // Otherwise if you are GBN, set all packets behind you to ack'ed
					// And slide the window
					for (index = 0; index < senderWinSize; index++) { 
						// Check packet number just as an extra precaution
						if (window.at(index) != NULL && (*window.at(index)).sent && (*window.at(index)).packetNum <= (*window.at(ack.seqNum % senderWinSize)).packetNum) {
							cout << "Previous Ack!!!" << endl;
							cout << "Next window: " << (expectedSeqNum + 1) % sequenceNumberRange << " - " << (((expectedSeqNum + 1) % sequenceNumberRange) + senderWinSize - 1) % sequenceNumberRange << endl;
							expectedSeqNum = (expectedSeqNum + 1) % sequenceNumberRange;
							// Reset packet
							(*window.at(index)).reset();
							(*window.at(index)).hasReset = true;
						}
					}

					// Unlock thread mutex so that the sender can update the fill the packet and send it.
					pthread_mutex_unlock(&senderMutex);
				}
			} else { // otherwise remove the error and ignore the ack
				(*window.at(index)).errorType = NO_ERR;
			}


			pthread_mutex_unlock(&windowMutex);
		}
	}

	delete[] ackBuff;

	return NULL;
}

// Thread for handling timeouts.
void* threadTimer(void* ti) {
	// TODO Comments
	// TODO print out where necessary
	
	// When the first packet is sent, start this, perhaps a 1 time use mutex or something
	// First sleep is for timeoutInterval
	// get the timeout time of the top packet, and keep track of it.
	// Use nanosleep or one of the other sleep functions to wait
	// When you wake up from sleep, start a time counter to use later; this counter can be done a few ways
	// for ever
		// If reset == true; set "reset" of packet to false, ignore packet
		// poll and pop from the queue any packet that has an ack or has been reset or are pointers to null
		// resend all packets who did not ack and have timeoutInterval equal to the timeout you got above, then also pop them
		// add all sent packets back into the queue
		// get the timeout for the next packet that was not poped
		// subtract the above stored timeout interval and the counter from it
		// that is the next sleep time
		// set the stored timeout to the timeout of the packet,
		// if sleep time is now less than or = 0, skip sleep
		// else sleep
	// end for
	// all threads should die when the parent dies.  No zombies.

	bool done = false;
	timespec timeInterval;
	timeInterval.tv_sec = 0;
	timeInterval.tv_nsec = timeoutInterval;

	sleep(1);
	for (;;) {
		nanosleep(&timeInterval, &timeInterval);
		while (!timeoutQueue.empty() || !done) {
			timespec currTime;
			clock_gettime(CLOCK_REALTIME, &currTime);
			Packet* top = timeoutQueue.top();

			timespec checkTime = diff((*top).timeout, currTime);
			if (checkTime.tv_sec > 1 || checkTime.tv_nsec > timeoutInterval) {
				// If the packet has been recently reset to blank or an ack has been recieved in the meantime; ignore
				if ((*top).ackRecieved) {
					timeoutQueue.pop();
				} else if ((*top).hasReset) {
					if ((*top).sent) {
						(*top).hasReset = false;
						timeoutQueue.push(top);
						timeoutQueue.pop();
					} else {
						(*top).hasReset = false;
						timeoutQueue.pop();
					}
				} else { // Resend
					cout << "!!!!!!!!! Packet " << (*top).seqNum << " has timed out; resent" << endl;

					// Set new timeout and send
					clock_gettime(CLOCK_REALTIME, &((*top).timeout));
					sock.sendTo((*top).buildPacket(), packetSize, servAddress, servPort);

					timeoutQueue.push(top);
					timeoutQueue.pop();
				}
			} else {
				done = true;
			}
		}

		done = false;
		timeInterval.tv_sec = 0;
	        timeInterval.tv_nsec = timeoutInterval;
	}

	return NULL;
}

timespec diff(timespec start, timespec end)
{
	timespec temp;
	if ((end.tv_nsec-start.tv_nsec)<0) {
		temp.tv_sec = end.tv_sec-start.tv_sec-1;
		temp.tv_nsec = 1000000000+end.tv_nsec-start.tv_nsec;
	} else {
		temp.tv_sec = end.tv_sec-start.tv_sec;
		temp.tv_nsec = end.tv_nsec-start.tv_nsec;
	}
	return temp;
}

